/*-------------------------------------------------------------------------
 *
 * params.c
 *	  支持查找与Param节点相关联的值。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/nodes/params.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/xact.h"
#include "fmgr.h"
#include "mb/stringinfo_mb.h"
#include "nodes/params.h"
#include "parser/parse_node.h"
#include "storage/shmem.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"


static void fc_paramlist_parser_setup(ParseState *fc_pstate, void *fc_arg);
static Node *fc_paramlist_param_ref(ParseState *fc_pstate, ParamRef *fc_pref);


/*
 * 分配并初始化一个新的 ParamListInfo 结构体。
 *
 * 要以“动态”方式（带钩子）创建新结构，请传递 0 作为 numParams，并手动设置 numParams。
 *
 * 默认的 parserSetup 函数会自动提供。如果调用方选择，可覆盖它。 （请注意，ParamListInfos 的大多数用例
 * 将永远不会使用 parserSetup 函数。）
 */
ParamListInfo makeParamList(int fc_numParams)
{
	ParamListInfo fc_retval;
	Size		fc_size;

	fc_size = offsetof(ParamListInfoData, params) +
		fc_numParams * sizeof(ParamExternData);

	fc_retval = (ParamListInfo) palloc(fc_size);
	fc_retval->paramFetch = NULL;
	fc_retval->paramFetchArg = NULL;
	fc_retval->paramCompile = NULL;
	fc_retval->paramCompileArg = NULL;
	fc_retval->parserSetup = fc_paramlist_parser_setup;
	fc_retval->parserSetupArg = (void *) fc_retval;
	fc_retval->paramValuesStr = NULL;
	fc_retval->numParams = fc_numParams;

	return fc_retval;
}

/*
 * 复制一个 ParamListInfo 结构体。
 *
 * 结果分配在 CurrentMemoryContext 中。
 *
 * 注意：此函数的目的是创建一个静态、自包含的参数值集合。如果存在动态参数钩子，我们
 * 会故意不将它们复制到结果中。相反，我们强制实例化所有可用的参数值并复制数据值。
 *
 * paramValuesStr 也不会被复制。
 */
ParamListInfo copyParamList(ParamListInfo fc_from)
{
	ParamListInfo fc_retval;

	if (fc_from == NULL || fc_from->numParams <= 0)
		return NULL;

	fc_retval = makeParamList(fc_from->numParams);

	for (int fc_i = 0; fc_i < fc_from->numParams; fc_i++)
	{
		ParamExternData *fc_oprm;
		ParamExternData *fc_nprm = &fc_retval->params[fc_i];
		ParamExternData fc_prmdata;
		int16		fc_typLen;
		bool		fc_typByVal;

		/* 给钩子一个机会，以防参数是动态的 */
		if (fc_from->paramFetch != NULL)
			fc_oprm = fc_from->paramFetch(fc_from, fc_i + 1, false, &fc_prmdata);
		else
			fc_oprm = &fc_from->params[fc_i];

		/* 扁平复制参数信息 */
		*fc_nprm = *fc_oprm;

		/* 需要 datumCopy 以防它是按引用传递的数据类型 */
		if (fc_nprm->isnull || !OidIsValid(fc_nprm->ptype))
			continue;
		get_typlenbyval(fc_nprm->ptype, &fc_typLen, &fc_typByVal);
		fc_nprm->value = datumCopy(fc_nprm->value, fc_typByVal, fc_typLen);
	}

	return fc_retval;
}


/*
 * 设置以解析包含来自 ParamListInfo 的参数引用的查询
 */
static void fc_paramlist_parser_setup(ParseState *fc_pstate, void *fc_arg)
{
	fc_pstate->p_paramref_hook = fc_paramlist_param_ref;
	/* 不需要使用 p_coerce_param_hook */
	fc_pstate->p_ref_hook_state = fc_arg;
}

/*
 * 使用来自 ParamListInfo 的参数类型数据转换 ParamRef。
 */
static Node * fc_paramlist_param_ref(ParseState *fc_pstate, ParamRef *fc_pref)
{
	ParamListInfo fc_paramLI = (ParamListInfo) fc_pstate->p_ref_hook_state;
	int			fc_paramno = fc_pref->number;
	ParamExternData *fc_prm;
	ParamExternData fc_prmdata;
	Param	   *fc_param;

	/* 检查参数编号是否有效 */
	if (fc_paramno <= 0 || fc_paramno > fc_paramLI->numParams)
		return NULL;

	/* 给钩子一个机会，以防参数是动态的 */
	if (fc_paramLI->paramFetch != NULL)
		fc_prm = fc_paramLI->paramFetch(fc_paramLI, fc_paramno, false, &fc_prmdata);
	else
		fc_prm = &fc_paramLI->params[fc_paramno - 1];

	if (!OidIsValid(fc_prm->ptype))
		return NULL;

	fc_param = makeNode(Param);
	fc_param->paramkind = PARAM_EXTERN;
	fc_param->paramid = fc_paramno;
	fc_param->paramtype = fc_prm->ptype;
	fc_param->paramtypmod = -1;
	fc_param->paramcollid = get_typcollation(fc_param->paramtype);
	fc_param->location = fc_pref->location;

	return (Node *) fc_param;
}

/*
 * 估算序列化一个 ParamListInfo 所需的空间。
 */
Size EstimateParamListSpace(ParamListInfo fc_paramLI)
{
	int			fc_i;
	Size		fc_sz = sizeof(int);

	if (fc_paramLI == NULL || fc_paramLI->numParams <= 0)
		return fc_sz;

	for (fc_i = 0; fc_i < fc_paramLI->numParams; fc_i++)
	{
		ParamExternData *fc_prm;
		ParamExternData fc_prmdata;
		Oid			fc_typeOid;
		int16		fc_typLen;
		bool		fc_typByVal;

		/* 给钩子一个机会，以防参数是动态的 */
		if (fc_paramLI->paramFetch != NULL)
			fc_prm = fc_paramLI->paramFetch(fc_paramLI, fc_i + 1, false, &fc_prmdata);
		else
			fc_prm = &fc_paramLI->params[fc_i];

		fc_typeOid = fc_prm->ptype;

		fc_sz = add_size(fc_sz, sizeof(Oid)); /* 类型 OID 的空间 */
		fc_sz = add_size(fc_sz, sizeof(uint16));	/* pflags 的空间 */

		/* datum/isnull 的空间 */
		if (OidIsValid(fc_typeOid))
			get_typlenbyval(fc_typeOid, &fc_typLen, &fc_typByVal);
		else
		{
			/* 如果没有类型 OID，假设为按值，像 copyParamList 所做的那样。 */
			fc_typLen = sizeof(Datum);
			fc_typByVal = true;
		}
		fc_sz = add_size(fc_sz,
					  datumEstimateSpace(fc_prm->value, fc_prm->isnull, fc_typByVal, fc_typLen));
	}

	return fc_sz;
}

/*
 * 将 ParamListInfo 结构序列化到调用者提供的存储中。
 *
 * 我们首先写入参数的数量，作为一个 4 字节整数，然后依次写入每个参数的详细信息。每个参数的详细信息
 * 由一个 4 字节的类型 OID、2 字节的标志，然后是由 datumSerialize() 序列化的数据值组成。调用者需要确保
 * 有足够的存储空间来存储将要写入的字节数；使用 EstimateParamListSpace 来找出需要多少字节。
 * *start_address 会更新为指向刚写入的字节之后的字节。
 *
 * RestoreParamList 可用来根据序列化表示重新创建一个 ParamListInfo；这将是一个静态、自包含的副本
 * 就像 copyParamList 会创建的那样。
 *
 * paramValuesStr 不包括在内。
 */
void SerializeParamList(ParamListInfo fc_paramLI, char **fc_start_address)
{
	int			fc_nparams;
	int			fc_i;

	/* 写入参数的数量。 */
	if (fc_paramLI == NULL || fc_paramLI->numParams <= 0)
		fc_nparams = 0;
	else
		fc_nparams = fc_paramLI->numParams;
	memcpy(*fc_start_address, &fc_nparams, sizeof(int));
	*fc_start_address += sizeof(int);

	/* 依次写入每个参数。 */
	for (fc_i = 0; fc_i < fc_nparams; fc_i++)
	{
		ParamExternData *fc_prm;
		ParamExternData fc_prmdata;
		Oid			fc_typeOid;
		int16		fc_typLen;
		bool		fc_typByVal;

		/* 给钩子一个机会，以防参数是动态的 */
		if (fc_paramLI->paramFetch != NULL)
			fc_prm = fc_paramLI->paramFetch(fc_paramLI, fc_i + 1, false, &fc_prmdata);
		else
			fc_prm = &fc_paramLI->params[fc_i];

		fc_typeOid = fc_prm->ptype;

		/* 写入类型 OID。 */
		memcpy(*fc_start_address, &fc_typeOid, sizeof(Oid));
		*fc_start_address += sizeof(Oid);

		/* 写入标志。 */
		memcpy(*fc_start_address, &fc_prm->pflags, sizeof(uint16));
		*fc_start_address += sizeof(uint16);

		/* 写入 datum/isnull。 */
		if (OidIsValid(fc_typeOid))
			get_typlenbyval(fc_typeOid, &fc_typLen, &fc_typByVal);
		else
		{
			/* 如果没有类型 OID，假设为按值，像 copyParamList 所做的那样。 */
			fc_typLen = sizeof(Datum);
			fc_typByVal = true;
		}
		datumSerialize(fc_prm->value, fc_prm->isnull, fc_typByVal, fc_typLen,
					   fc_start_address);
	}
}

/*
 * 复制一个 ParamListInfo 结构体。
 *
 * 结果分配在 CurrentMemoryContext 中。
 *
 * 注意：此函数的目的是创建一个静态、自包含的参数值集合。如果存在动态参数钩子，我们
 * 会故意不将它们复制到结果中。相反，我们强制实例化所有可用的参数值并复制数据值。
 */
ParamListInfo RestoreParamList(char **fc_start_address)
{
	ParamListInfo fc_paramLI;
	int			fc_nparams;

	memcpy(&fc_nparams, *fc_start_address, sizeof(int));
	*fc_start_address += sizeof(int);

	fc_paramLI = makeParamList(fc_nparams);

	for (int fc_i = 0; fc_i < fc_nparams; fc_i++)
	{
		ParamExternData *fc_prm = &fc_paramLI->params[fc_i];

		/* 读取类型 OID。 */
		memcpy(&fc_prm->ptype, *fc_start_address, sizeof(Oid));
		*fc_start_address += sizeof(Oid);

		/* 读取标志。 */
		memcpy(&fc_prm->pflags, *fc_start_address, sizeof(uint16));
		*fc_start_address += sizeof(uint16);

		/* 读取 datum/isnull。 */
		fc_prm->value = datumRestore(fc_start_address, &fc_prm->isnull);
	}

	return fc_paramLI;
}

/*
 * BuildParamLogString
 *		返回表示参数列表的字符串，用于日志记录。
 *
 * 如果调用者已经知道某些参数的文本表示，它可以传递一个正好包含 params->numParams 值的数组
 * 作为 knownTextValues，该数组可以为任何未知的单个值包含 NULL。如果没有已知的参数，可以给出 NULL。
 *
 * 如果 maxlen >= 0，则这是要打印的任何一个参数值的最大字节数；如果字符串更长，将添加省略号。
 * （添加的引号不计入此计算。）
 */
char * BuildParamLogString(ParamListInfo fc_params, char **fc_knownTextValues, int fc_maxlen)
{
	MemoryContext fc_tmpCxt,
				fc_oldCxt;
	StringInfoData fc_buf;

	/*
	 * 注意：不要考虑返回 params->paramValuesStr！它可能是使用不同的 maxlen 生成的，因此不合适。
	 * 除此之外，这是用于创建该字符串的函数。
	 */

	/*
	 * 如果正在使用参数获取钩子，则不进行任何工作。此外，无法在中止的事务中执行此操作。
	 * （当存在某些 knownTextValues 时，可能有可能改进这一最后一点，但这似乎比较棘手。）
	 */
	if (fc_params->paramFetch != NULL ||
		IsAbortedTransactionBlockState())
		return NULL;

	/* 在调用者的内存上下文中初始化输出字符串信息 */
	initStringInfo(&fc_buf);

	/* 使用临时上下文来调用输出函数，以防万一 */
	fc_tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
								   "BuildParamLogString",
								   ALLOCSET_DEFAULT_SIZES);
	fc_oldCxt = MemoryContextSwitchTo(fc_tmpCxt);

	for (int fc_paramno = 0; fc_paramno < fc_params->numParams; fc_paramno++)
	{
		ParamExternData *fc_param = &fc_params->params[fc_paramno];

		appendStringInfo(&fc_buf,
						 "%s$%d = ",
						 fc_paramno > 0 ? ", " : "",
						 fc_paramno + 1);

		if (fc_param->isnull || !OidIsValid(fc_param->ptype))
			appendStringInfoString(&fc_buf, "NULL");
		else
		{
			if (fc_knownTextValues != NULL && fc_knownTextValues[fc_paramno] != NULL)
				appendStringInfoStringQuoted(&fc_buf, fc_knownTextValues[fc_paramno],
											 fc_maxlen);
			else
			{
				Oid			fc_typoutput;
				bool		fc_typisvarlena;
				char	   *fc_pstring;

				getTypeOutputInfo(fc_param->ptype, &fc_typoutput, &fc_typisvarlena);
				fc_pstring = OidOutputFunctionCall(fc_typoutput, fc_param->value);
				appendStringInfoStringQuoted(&fc_buf, fc_pstring, fc_maxlen);
			}
		}
	}

	MemoryContextSwitchTo(fc_oldCxt);
	MemoryContextDelete(fc_tmpCxt);

	return fc_buf.data;
}

/*
 * ParamsErrorCallback - 用于在错误上下文中打印参数的回调
 *
 * 请注意，除非事先调用了 BuildParamLogString，否则这是一个无操作。
 */
void ParamsErrorCallback(void *fc_arg)
{
	ParamsErrorCbData *fc_data = (ParamsErrorCbData *) fc_arg;

	if (fc_data == NULL ||
		fc_data->params == NULL ||
		fc_data->params->paramValuesStr == NULL)
		return;

	if (fc_data->portalName && fc_data->portalName[0] != '\0')
		errcontext("portal \"%s\" with parameters: %s",
				   fc_data->portalName, fc_data->params->paramValuesStr);
	else
		errcontext("unnamed portal with parameters: %s",
				   fc_data->params->paramValuesStr);
}
